/**
 * Service Manager
 * Manages integration between HTTP and MCP services
 */

const HttpServerManager = require('../http/server');
const McpServerManager = require('../mcp/server');
const ConfigManager = require('../config/config-manager');
const { ErrorFactory, ErrorHandler } = require('../utils/errors');
const { getLogger } = require('../logging/logger');
const BrowserUtil = require('../utils/browser');
const FirstRunDetector = require('../utils/first-run');

const logger = getLogger();

/**
 * Service Manager class
 * Coordinates HTTP and MCP server lifecycle and configuration
 */
class ServiceManager {
  constructor(config = {}) {
    // Default configuration
    this.config = {
      http: {
        port: 3000,
        host: 'localhost',
        auto_start: true,
        cors: { enabled: true, origins: ['*'] }
      },
      mcp: {
        name: 'vision-analyzer',
        version: '1.0.0',
        timeout: 30000,
        transport: 'stdio'
      },
      browser: {
        auto_open: true,
        first_run_only: true,
        delay: 1500,
        max_retries: 3
      },
      ...config
    };

    // Service instances
    this.httpServer = new HttpServerManager(this.config.http);
    this.mcpServer = new McpServerManager(this.config.mcp);
    this.configManager = new ConfigManager();

    // Service state
    this.isRunning = false;
    this.startTime = null;
    this.shutdownInProgress = false;

    // Service metrics
    this.metrics = {
      startTime: null,
      restarts: 0,
      totalUptime: 0,
      lastHealthCheck: null
    };

    // Set up process signal handlers
    this._setupProcessHandlers();

    logger.info('Service manager initialized');
  }

  /**
   * Start both HTTP and MCP services
   * @param {Object} options - Start options
   * @returns {Promise<Object>} Start result
   */
  async startServices(options = {}) {
    if (this.isRunning) {
      return {
        success: false,
        error: 'Services are already running',
        status: await this.getServiceStatus()
      };
    }

    logger.info('Starting HTTP and MCP services...');

    try {
      // Load configuration
      const config = await this.configManager.getConfig();
      this._updateConfiguration(config);

      const results = {
        http: null,
        mcp: null,
        startTime: new Date(),
        errors: []
      };

      // Start HTTP server first
      try {
        logger.info('Starting HTTP server...');
        results.http = await this.httpServer.startServer();

        if (results.http.success) {
          logger.info(`HTTP server started on port ${results.http.port}`);
        } else {
          throw ErrorFactory.configuration(`HTTP server failed to start: ${results.http.error}`, { service: 'http' });
        }

      } catch (error) {
        results.errors.push(`HTTP server failed: ${error.message}`);
        logger.error('HTTP server start failed', error);
      }

      // Start MCP server
      try {
        logger.info('Starting MCP server...');
        results.mcp = await this.mcpServer.startServer(options.mcp);

        if (results.mcp.success) {
          logger.info(`MCP server started: ${this.config.mcp.name} v${this.config.mcp.version}`);
        } else {
          throw ErrorFactory.mcp(`MCP server failed to start: ${results.mcp.error}`, 'service-start');
        }

      } catch (error) {
        results.errors.push(`MCP server failed: ${error.message}`);
        logger.error('MCP server start failed', error);
      }

      // Determine overall success
      const httpStarted = results.http && results.http.success;
      const mcpStarted = results.mcp && results.mcp.success;
      const bothStarted = httpStarted && mcpStarted;

      if (bothStarted) {
        this.isRunning = true;
        this.startTime = results.startTime;
        this.metrics.startTime = this.startTime;

        logger.info('All services started successfully');
        // Handle automatic browser opening for first-time setup
        await this._handleAutoBrowserOpen(results.http);

        return {
          success: true,
          http: results.http,
          mcp: results.mcp,
          startTime: this.startTime,
          status: 'healthy'
        };

      } else {
        // Cleanup any partially started services
        await this._cleanupPartialStart(results);

        return {
          success: false,
          error: 'Failed to start all services',
          http: results.http,
          mcp: results.mcp,
          errors: results.errors,
          status: 'failed'
        };
      }

    } catch (error) {
      logger.error('Service start failed', error);

      return {
        success: false,
        error: error.message,
        details: ErrorHandler.handle(error, { operation: 'service_start' })
      };
    }
  }

  /**
   * Stop both HTTP and MCP services
   * @returns {Promise<Object>} Stop result
   */
  async stopServices() {
    if (!this.isRunning) {
      return {
        success: true,
        message: 'Services are not running'
      };
    }

    if (this.shutdownInProgress) {
      return {
        success: false,
        error: 'Shutdown already in progress'
      };
    }

    this.shutdownInProgress = true;
    logger.info('Stopping HTTP and MCP services...');

    try {
      const results = {
        http: null,
        mcp: null,
        stopTime: new Date(),
        errors: []
      };

      const stopPromises = [];

      // Stop HTTP server
      stopPromises.push(
        this.httpServer.stopServer()
          .then(result => {
            results.http = result;
            logger.info('HTTP server stopped');
          })
          .catch(error => {
            results.errors.push(`HTTP server stop failed: ${error.message}`);
            logger.error('HTTP server stop failed', error);
          })
      );

      // Stop MCP server
      stopPromises.push(
        this.mcpServer.stopServer()
          .then(result => {
            results.mcp = result;
            logger.info('MCP server stopped');
          })
          .catch(error => {
            results.errors.push(`MCP server stop failed: ${error.message}`);
            logger.error('MCP server stop failed', error);
          })
      );

      // Wait for both services to stop
      await Promise.allSettled(stopPromises);

      // Update state
      this.isRunning = false;
      this.shutdownInProgress = false;

      if (this.startTime) {
        this.metrics.totalUptime += Date.now() - this.startTime.getTime();
      }

      this.startTime = null;

      const overallSuccess = results.errors.length === 0;

      logger.info(`Services stopped ${overallSuccess ? 'successfully' : 'with errors'}`);

      return {
        success: overallSuccess,
        http: results.http,
        mcp: results.mcp,
        stopTime: results.stopTime,
        errors: results.errors,
        uptime: this.metrics.totalUptime
      };

    } catch (error) {
      this.shutdownInProgress = false;
      logger.error('Service stop failed', error);

      return {
        success: false,
        error: error.message,
        details: ErrorHandler.handle(error, { operation: 'service_stop' })
      };
    }
  }

  /**
   * Restart both services
   * @param {Object} options - Restart options
   * @returns {Promise<Object>} Restart result
   */
  async restartServices(options = {}) {
    logger.info('Restarting HTTP and MCP services...');

    this.metrics.restarts++;

    // Stop services first
    const stopResult = await this.stopServices();
    if (!stopResult.success && stopResult.errors.length > 0) {
      logger.warn('Service stop encountered errors during restart', stopResult.errors);
    }

    // Wait a moment before restarting
    await new Promise(resolve => setTimeout(resolve, 1000));

    // Start services again
    const startResult = await this.startServices(options);

    return {
      success: startResult.success,
      restartCount: this.metrics.restarts,
      stopResult: stopResult,
      startResult: startResult,
      restartTime: new Date()
    };
  }

  /**
   * Get comprehensive service status
   * @returns {Promise<Object>} Service status
   */
  async getServiceStatus() {
    const httpStatus = this.httpServer.getServerStatus();
    const mcpStatus = this.mcpServer.getServerStatus();

    // Determine overall health
    let overall = 'stopped';
    if (this.isRunning) {
      if (httpStatus.running && mcpStatus.running) {
        overall = 'healthy';
      } else if (httpStatus.running || mcpStatus.running) {
        overall = 'degraded';
      } else {
        overall = 'unhealthy';
      }
    }

    this.metrics.lastHealthCheck = new Date();

    return {
      overall: overall,
      running: this.isRunning,
      startTime: this.startTime,
      uptime: this.startTime ? Date.now() - this.startTime.getTime() : 0,
      version: {
        http: '1.0.0',
        mcp: this.config.mcp.version,
        service: '1.0.0'
      },
      http: httpStatus,
      mcp: mcpStatus,
      metrics: this.getMetrics()
    };
  }

  /**
   * Get health status
   * @returns {Promise<Object>} Health status
   */
  async getHealthStatus() {
    const status = await this.getServiceStatus();

    return {
      overall: status.overall,
      http: {
        running: status.http.running,
        uptime: status.http.uptime,
        lastCheck: new Date()
      },
      mcp: {
        running: status.mcp.running,
        uptime: status.mcp.uptime,
        lastCheck: new Date()
      },
      timestamp: new Date()
    };
  }

  /**
   * Get service metrics
   * @returns {Object} Service metrics
   */
  getMetrics() {
    return {
      ...this.metrics,
      currentUptime: this.startTime ? Date.now() - this.startTime.getTime() : 0,
      restarts: this.metrics.restarts,
      http: this.httpServer.getMetrics ? this.httpServer.getMetrics() : {},
      mcp: this.mcpServer.getMetrics(),
      memory: process.memoryUsage(),
      nodeVersion: process.version,
      platform: process.platform
    };
  }

  /**
   * Get service metrics (alias for getMetrics)
   * @returns {Object} Service metrics
   */
  async getServiceMetrics() {
    return this.getMetrics();
  }

  /**
   * Update service configuration
   * @param {Object} newConfig - New configuration
   * @returns {Promise<Object>} Update result
   */
  async updateConfiguration(newConfig) {
    try {
      const oldConfig = { ...this.config };

      // Update configuration
      if (newConfig.http) {
        this.config.http = { ...this.config.http, ...newConfig.http };
        this.httpServer.updateConfiguration(newConfig.http);
      }

      if (newConfig.mcp) {
        this.config.mcp = { ...this.config.mcp, ...newConfig.mcp };
        this.mcpServer.updateConfiguration(newConfig.mcp);
      }

      logger.info('Service configuration updated');

      return {
        success: true,
        oldConfig: oldConfig,
        newConfig: this.config,
        needsRestart: this._configRequiresRestart(oldConfig, this.config)
      };

    } catch (error) {
      logger.error('Configuration update failed', error);

      return {
        success: false,
        error: error.message,
        details: ErrorHandler.handle(error, { operation: 'config_update' })
      };
    }
  }

  /**
   * Get effective configuration
   * @returns {Promise<Object>} Effective configuration
   */
  async getEffectiveConfiguration() {
    const config = await this.configManager.getConfig();

    return {
      http: {
        ...this.config.http,
        ...config.http_server
      },
      mcp: {
        ...this.config.mcp,
        ...config.mcp
      },
      merged: {
        ...config,
        services: {
          http: this.config.http,
          mcp: this.config.mcp
        }
      }
    };
  }

  /**
   * Attempt to recover services
   * @returns {Promise<Object>} Recovery result
   */
  async recoverServices() {
    logger.info('Attempting service recovery...');

    const recoveryResult = {
      attempted: true,
      timestamp: new Date(),
      servicesRecovered: {},
      errors: []
    };

    try {
      const currentStatus = await this.getServiceStatus();

      // Recover HTTP server if needed
      if (!currentStatus.http.running) {
        try {
          await this.httpServer.startServer();
          recoveryResult.servicesRecovered.http = true;
          logger.info('HTTP server recovered');
        } catch (error) {
          recoveryResult.servicesRecovered.http = false;
          recoveryResult.errors.push(`HTTP recovery failed: ${error.message}`);
        }
      } else {
        recoveryResult.servicesRecovered.http = true;
      }

      // Recover MCP server if needed
      if (!currentStatus.mcp.running) {
        try {
          await this.mcpServer.startServer();
          recoveryResult.servicesRecovered.mcp = true;
          logger.info('MCP server recovered');
        } catch (error) {
          recoveryResult.servicesRecovered.mcp = false;
          recoveryResult.errors.push(`MCP recovery failed: ${error.message}`);
        }
      } else {
        recoveryResult.servicesRecovered.mcp = true;
      }

      // Update running state based on recovery
      const recoveredServices = Object.values(recoveryResult.servicesRecovered).filter(Boolean).length;
      const totalServices = Object.keys(recoveryResult.servicesRecovered).length;

      if (recoveredServices === totalServices) {
        this.isRunning = true;
        if (!this.startTime) {
          this.startTime = new Date();
        }
      }

      recoveryResult.success = recoveredServices === totalServices;
      recoveryResult.servicesRecoveredCount = recoveredServices;
      recoveryResult.totalServices = totalServices;

    } catch (error) {
      recoveryResult.success = false;
      recoveryResult.errors.push(`Recovery process failed: ${error.message}`);
      logger.error('Service recovery failed', error);
    }

    return recoveryResult;
  }

  /**
   * Graceful shutdown handler
   * @param {string} signal - Signal name
   * @returns {Promise<void>}
   */
  async gracefulShutdown(signal) {
    if (this.shutdownInProgress) {
      logger.warn('Shutdown already in progress, ignoring signal');
      return;
    }

    logger.info(`Received ${signal}, initiating graceful shutdown...`);

    try {
      const result = await this.stopServices();

      if (result.success) {
        logger.info('Graceful shutdown completed successfully');
        // Only exit if not in test environment
        if (process.env.NODE_ENV !== 'test') {
          process.exit(0);
        }
      } else {
        logger.error('Graceful shutdown completed with errors', result.errors);
        // Only exit if not in test environment
        if (process.env.NODE_ENV !== 'test') {
          process.exit(1);
        }
      }

    } catch (error) {
      logger.error('Graceful shutdown failed', error);
      // Only exit if not in test environment
      if (process.env.NODE_ENV !== 'test') {
        process.exit(1);
      }
    }
  }

  /**
   * Set up process signal handlers
   * @private
   */
  _setupProcessHandlers() {
    // 在测试环境中，避免设置过多的进程监听器以防止内存泄漏警告
    const isTestEnvironment = process.env.NODE_ENV === 'test' ||
                            process.env.JEST_WORKER_ID !== undefined ||
                            process.env.npm_config_argv?.includes('test');

    if (isTestEnvironment) {
      // 在测试环境中，只设置必要的监听器，并增加最大监听器数量
      if (process.listenerCount('SIGINT') === 0) {
        process.once('SIGINT', () => this.gracefulShutdown('SIGINT'));
      }
      if (process.listenerCount('SIGTERM') === 0) {
        process.once('SIGTERM', () => this.gracefulShutdown('SIGTERM'));
      }
      if (process.listenerCount('uncaughtException') === 0) {
        process.once('uncaughtException', (error) => {
          logger.error('Uncaught exception:', error);
          this.gracefulShutdown('uncaughtException');
        });
      }
      if (process.listenerCount('unhandledRejection') === 0) {
        process.once('unhandledRejection', (reason, promise) => {
          logger.error('Unhandled rejection at:', promise, 'reason:', reason);
          this.gracefulShutdown('unhandledRejection');
        });
      }

      // 在测试环境中增加最大监听器数量
      process.setMaxListeners(20);
    } else {
      // 生产环境中的正常设置
      process.once('SIGINT', () => this.gracefulShutdown('SIGINT'));
      process.once('SIGTERM', () => this.gracefulShutdown('SIGTERM'));

      // Handle uncaught exceptions
      process.once('uncaughtException', (error) => {
        logger.error('Uncaught exception:', error);
        this.gracefulShutdown('uncaughtException');
      });

      // Handle unhandled rejections
      process.once('unhandledRejection', (reason, promise) => {
        logger.error('Unhandled rejection at:', promise, 'reason:', reason);
        this.gracefulShutdown('unhandledRejection');
      });
    }
  }

  /**
   * Update internal configuration from config manager
   * @param {Object} config - Configuration object
   * @private
   */
  _updateConfiguration(config) {
    if (config.http_server) {
      this.config.http = { ...this.config.http, ...config.http_server };
      this.httpServer.updateConfiguration(config.http_server);
    }

    if (config.mcp) {
      this.config.mcp = { ...this.config.mcp, ...config.mcp };
      this.mcpServer.updateConfiguration(config.mcp);
    }
  }

  /**
   * Check if configuration change requires restart
   * @param {Object} oldConfig - Old configuration
   * @param {Object} newConfig - New configuration
   * @returns {boolean} Whether restart is required
   * @private
   */
  _configRequiresRestart(oldConfig, newConfig) {
    // HTTP server changes that require restart
    if (oldConfig.http.port !== newConfig.http.port ||
        oldConfig.http.host !== newConfig.http.host) {
      return true;
    }

    // MCP server changes that require restart
    if (oldConfig.mcp.name !== newConfig.mcp.name ||
        oldConfig.mcp.version !== newConfig.mcp.version ||
        oldConfig.mcp.transport !== newConfig.mcp.transport) {
      return true;
    }

    return false;
  }

  /**
   * Clean up partially started services
   * @param {Object} results - Start results
   * @private
   */
  async _cleanupPartialStart(results) {
    logger.info('Cleaning up partially started services...');

    const cleanupPromises = [];

    if (results.http && results.http.success) {
      cleanupPromises.push(
        this.httpServer.stopServer().catch(error => {
          logger.error('Failed to cleanup HTTP server during partial start cleanup', error);
        })
      );
    }

    if (results.mcp && results.mcp.success) {
      cleanupPromises.push(
        this.mcpServer.stopServer().catch(error => {
          logger.error('Failed to cleanup MCP server during partial start cleanup', error);
        })
      );
    }

    await Promise.allSettled(cleanupPromises);
    logger.info('Partial start cleanup completed');
  }

  /**
   * Handle automatic browser opening for first-time setup
   * @param {Object} httpResult - HTTP server start result
   * @private
   */
  async _handleAutoBrowserOpen(httpResult) {
    if (!this.config.browser.auto_open || !httpResult.success) {
      return;
    }

    try {
      // Check if this is first run
      const isFirstRun = await FirstRunDetector.isFirstRun();

      // If configured to only open on first run and this isn't first run, skip
      if (this.config.browser.first_run_only && !isFirstRun) {
        logger.debug('Skipping browser auto-open (not first run)');
        return;
      }

      // Construct URL
      const protocol = 'http';
      const host = httpResult.address?.address || this.config.http.host || 'localhost';
      const port = httpResult.address?.port || httpResult.port || this.config.http.port;
      const url = `${protocol}://${host}:${port}`;

      logger.info('Auto-opening browser for first-time setup', { url, isFirstRun });

      // Open browser with retry logic
      const success = await BrowserUtil.openBrowserWithRetry(url, {
        delay: this.config.browser.delay,
        maxRetries: this.config.browser.max_retries
      });

      if (success) {
        logger.info('Browser opened successfully for first-time setup');

        // Mark first run as completed if browser opened successfully
        if (isFirstRun) {
          await FirstRunDetector.markFirstRunCompleted({
            version: this.config.mcp.version,
            setupMethod: 'auto_browser'
          });
        }
      } else {
        logger.warn('Failed to open browser for first-time setup');

        // Provide manual instructions
        console.log(`\n🌐 Vision Analyzer Configuration Interface`);
        console.log(`═══════════════════════════════════════════════════`);
        console.log(`\n📝 Please open your browser and navigate to:`);
        console.log(`\n   ${url}\n`);
        console.log(`💡 If the browser doesn't open automatically, please copy and paste the URL above.\n`);

        // Log the command for manual execution
        const manualCommand = BrowserUtil.getBrowserCommand(url);
        logger.info('Manual browser command:', manualCommand);
      }

    } catch (error) {
      logger.error('Auto browser open failed', error);

      // Don't fail service startup due to browser issues
      console.log(`\n⚠️  Could not open browser automatically`);
      console.log(`Please manually navigate to the configuration interface.\n`);
    }
  }
}

module.exports = ServiceManager;